package com.hhf.rpa.job.config;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.hhf.rpa.job.entity.BaseJob;
import com.hhf.rpa.job.service.BaseJobHandler;
import com.hhf.rpa.job.service.BaseJobService;
import com.hhf.rpa.util.StringUtils;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;

import javax.annotation.PreDestroy;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.*;

@Slf4j
@Setter
@Configuration
@EnableScheduling
public class BaseJobConfig implements SchedulingConfigurer {
    @Autowired
    private BaseJobService baseJobService;
    @Autowired
    private BaseJobHandler baseJobHandler;

    private volatile ScheduledTaskRegistrar taskRegistrar;

    /**
     * 缓存当前调度任务线程中所有待执行的job任务
     */
    private final ConcurrentHashMap<Integer, ScheduledFuture<?>> scheduledFutures = new ConcurrentHashMap<>();

    /**
     * 缓存BaseJob配置信息，用来检测判断BaseJob配置有没有修改变更
     */
    private final ConcurrentHashMap<Integer, BaseJob> tasks = new ConcurrentHashMap<>();

    private static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Bean
    public Executor taskScheduler() {
        //设置线程名称，创建ScheduledExecutorService线程池
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("baseJobPool-%d").build();
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5, namedThreadFactory);
        return scheduledExecutorService;
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskScheduler());
        this.taskRegistrar = taskRegistrar;
//        refresh();
    }

    @PreDestroy
    public void destory() {
        taskRegistrar.destroy();
    }

    public void refreshAll() {
        refresh();
    }

    public void refresh() {
        List<BaseJob> jobs = baseJobService.getJob();
        Set<Integer> ids = scheduledFutures.keySet();
        for (Integer id : ids) {
            if (!isExist(id, jobs)) {
                scheduledFutures.get(id).cancel(false);
                scheduledFutures.remove(id);
            }
        }

        for (BaseJob job: jobs) {
            if(StringUtils.isBlank(job.getCron())) {
                continue;
            }
            Integer jobId = job.getJobId();
            if(scheduledFutures.containsKey(jobId) && tasks.get(jobId).equals(job)) {
                continue;
            }
            if (scheduledFutures.containsKey(jobId)) {
                scheduledFutures.get(jobId).cancel(false);
                scheduledFutures.remove(jobId);
                tasks.remove(jobId);
            }
            ScheduledFuture<?> schedule = taskRegistrar.getScheduler().schedule(getRunnable(job), getTrigger(job));
            scheduledFutures.put(jobId,schedule);
            tasks.put(jobId,job);
        }
    }

    private Runnable getRunnable(BaseJob baseJob) {
        return () -> baseJobHandler.execHandler(baseJob,1);
    }

    private Trigger getTrigger(BaseJob job) {
        return triggerContext -> {
            CronTrigger trigger = new CronTrigger(job.getCron());
            Date nextExec = trigger.nextExecutionTime(triggerContext);

            baseJobService.updateNextExecTime(job.getJobId(), nextExec);
            log.debug("任务【{}(任务ID：{})】下一次执行时间:{}",job.getJobName(),job.getJobId(),sdf.format(nextExec));
            return nextExec;
        };
    }

    private boolean isExist(Integer id, List<BaseJob> jobs) {
        for(BaseJob job: jobs) {
            if(id.equals(job.getJobId())) {
                return true;
            }
        }
        return false;
    }

}
